import org.locationtech.jts.geom.*;

import java.text.ParseException;
import java.util.*;

public class ShadowMain {

    public static void main(String[] args) throws ParseException {
        List<PointVO> red = new ArrayList<>();
        red.add(new PointVO(500,500,9));
        red.add(new PointVO(500,-500,9));
        red.add(new PointVO(-500,-500,0));
        red.add(new PointVO(-500,500,0));
        red.add(new PointVO(500,500,5));

        List<PointVO> obs = new ArrayList<>();
        obs.add(new PointVO(5,5,8));
        obs.add(new PointVO(5,-5,8));
        obs.add(new PointVO(-5,-5,8));
        obs.add(new PointVO(-5,5,8));
        obs.add(new PointVO(5,5,8));

        VectorVO vectorVO = CommonUtil.planeEquation(red);
        Geometry shadowArea = getShadowArea(31.2, obs, vectorVO, red);
        System.out.println(shadowArea.toText());
    }

    /**
     * 阴影
     * @param lat  纬度
     * @param points 障碍物坐标
     * @param vectorPo 投影面的平面方程
     * @param allArea  整个屋面坐标
     * @return
     * @throws ParseException
     */
    public static Geometry getShadowArea(double lat, List<PointVO> points, VectorVO vectorPo, List<PointVO> allArea) throws ParseException {
        List<String> times = winterOrSumerTimeFor22(lat);
        if(points.size()==0){
            return null;
        }
        PointVO point = points.get(0);
        if(!points.get(0).equals(points.get(points.size()-1))){
            points.add(points.get(0));
        }
        Polygon polygon = PointVO.createPolygon(points);
        double minY = 0;
        Geometry geometry = polygon.convexHull();
        List<Geometry> list = GeometryUtil.getList(geometry);
        ArrayList<PointVO> points1 = new ArrayList<>();
        if(list.size()>0){
            Geometry geometry1 = list.get(0);
            Coordinate[] coordinates = geometry1.getCoordinates();
            for (int i = 0; i < coordinates.length; i++) {
                points1.add(new PointVO(coordinates[i].getX(),coordinates[i].getY(),point.z));
            }
            minY = point.z;
        }
        List<List<PointVO>> edges = generateEdges(points1,vectorPo);
        Geometry polyhedralShadowNewForWall = getPolyhedralShadow(lat, edges, times, vectorPo,allArea,minY);
        return polyhedralShadowNewForWall;
    }

    public static Geometry getPolyhedralShadow(double lat,List<List<PointVO>> edges,List<String> times,VectorVO vectorPo,List<PointVO> allArea,double minY) throws ParseException {
        ArrayList<Geometry> geometries = new ArrayList<>();
        for (int i = 0; i < times.size(); i++) {
            String s = times.get(i);
            ArrayList<String> strings = new ArrayList<>();
            strings.add(s);
            Geometry polyhedralShadow = getPolyhedralShadowForWall(minY, lat, edges,strings,vectorPo);
            if(polyhedralShadow!=null&&!polyhedralShadow.isEmpty()) {
                List<Geometry> list = GeometryUtil.getList(polyhedralShadow);
                list.forEach(l->{
                    String geometryType = l.getGeometryType();
                    if(geometryType.equals(Geometry.TYPENAME_POLYGON)) {
                        geometries.add((Polygon) l);
                    }
                });
            }
        }

        Geometry[] geometriesArray=new Geometry[geometries.size()];
        geometries.toArray(geometriesArray);
        GeometryCollection gc = new GeometryFactory().createGeometryCollection(geometriesArray);
        Geometry union = gc.union();
        if(!allArea.get(0).equals(allArea.get(allArea.size()-1))){
            allArea.add(allArea.get(0));
        }
        Polygon polygon1 = PointVO.createPolygon(allArea);
        if(polygon1.intersects(union)){
            try {
                Geometry intersection = polygon1.intersection(union);
                return intersection;
            }catch (Exception e){
                return union;
            }

        }
        return union;
    }

    public static Geometry getPolyhedralShadowForWall(double miny,double lat,List<List<PointVO>> edges,List<String> times,VectorVO vectorPo) throws ParseException {
        for (String time:times
        ) {
            List<PointVO> points = calculateProjectionForVect(miny,time, lat, edges, vectorPo);
            if(points.size()>2){
                points.add(points.get(0));
                Polygon polygon = PointVO.createPolygon(points);
                return polygon;
            }
        }
        return null;

    }

    public static List<PointVO> calculateProjectionForVect(double height,String time, double latitude, List<List<PointVO>> edges, VectorVO vectorPo) throws ParseException {
        //获取太阳高度角和方位角
        AngleVO aspect = CommonUtil.getAspect(time, latitude);
        //边集合 转成点集合
        Set<PointVO> set = new LinkedHashSet<>();
        for (List<PointVO> var:edges
        ) {
            set.add(var.get(0));
            set.add(var.get(1));
        }
        ArrayList<PointVO> points = new ArrayList<>(set);
        //计算太阳光线方向向量
        VectorVO vectorByDirection = CommonUtil.getVectorByDirection(aspect.getAltitude(), aspect.getAzimuth());
        //构造4维坐标
        List<double[]> nPoints = new ArrayList<>();
        //转成4维坐标
        points.forEach(calendarPoint -> {
            double[] li1 = {calendarPoint.getX(), calendarPoint.getY(), calendarPoint.getZ(), 1};
            nPoints.add(li1);
        });
        //构建投影矩阵
        double[][] matArrays = CommonUtil.buildProjectionMat(vectorPo, vectorByDirection);
        //坐标2维数组
        double[][] doubles5 = CommonUtil.changeListToArrays1(nPoints);
        //矩阵倒转
        double[][] doubles6 = CommonUtil.transposeMatrix(doubles5);
        //矩阵乘积
        double[][] dot1 = CommonUtil.dot(matArrays, doubles6);
        //矩阵倒转
        double[][] doubles7 = CommonUtil.transposeMatrix(dot1);
        //矩阵缩放
        double[][] suo = CommonUtil.suo(doubles7);
        ArrayList<PointVO> newli = new ArrayList<>();
        ArrayList<PointVO> rotateLi = new ArrayList<>();
        for (int i = 0; i < suo.length; i++) {
            double[] doubles = suo[i];
            newli.add(new PointVO(doubles[0],doubles[1],doubles[2]));
            rotateLi.add(new PointVO(doubles[0],doubles[1],0));
        }

        //旋转后坐标与投影坐标关系字典
        HashMap<PointVO, PointVO> relationDctProjection = new HashMap<>();
        //原始坐标与旋转后坐标点关系字典
        HashMap<PointVO, PointVO>  relationDctRotate = new HashMap<>();

        for (int i = 0; i < points.size(); i++) {
            double v = rotateLi.get(i).x;
            double v1 = rotateLi.get(i).y;
            relationDctProjection.put(new PointVO(v,v1),newli.get(i));
            relationDctRotate.put(points.get(i),rotateLi.get(i));
        }
        ArrayList<List<PointVO>> rotatedEdges2d = new ArrayList<>();
        edges.forEach(e->{
            List<PointVO> temp1 = new ArrayList<>();
            e.forEach(p->{
                PointVO point = relationDctRotate.get(p);
                temp1.add(new PointVO(point.getX(),point.getY()));
            });
            rotatedEdges2d.add(temp1);
        });
        List<PointVO> boundary = getBoundaryV1(rotatedEdges2d);
        List<PointVO> boundaryBak = new ArrayList<>();
        boundary.forEach(point->{
            double z = getZ(point, vectorPo);
            if(z<height){
                boundaryBak.add(new PointVO(point.x, point.y, z));
            }
        });
        return boundaryBak;
    }

    public static double getZ(PointVO p,VectorVO vectorPo){
        double v = vectorPo.getA() * p.x + vectorPo.getB() * p.y + vectorPo.getD();
        double z = -1.0 * v / vectorPo.getC();
        return z;
    }

    public static List<PointVO> getBoundaryV1(List<List<PointVO>> edges){
        List<PointVO> boundList = new ArrayList<>();
        List<List<PointVO>> edgesLists = reorganizeSegments(edges);
        HashMap<String, List<PointVO>> relationPoint = new HashMap<>();
        for (List<PointVO> e:edgesLists
        ) {
            PointVO p1 = e.get(0);
            PointVO p2 = e.get(1);
            List<PointVO> points = relationPoint.get(p1.toString());
            if(points!=null&&points.size()>0){
                points.add(p2);
                relationPoint.put(p1.toString(),points);
            }else {
                points=new ArrayList<>();
                points.add(p2);
                relationPoint.put(p1.toString(),points);
            }
            List<PointVO> point2s = relationPoint.get(p2.toString());
            if(point2s!=null&&point2s.size()>0){
                point2s.add(p1);
                relationPoint.put(p2.toString(),point2s);
            }else {
                point2s=new ArrayList<>();
                point2s.add(p1);
                relationPoint.put(p2.toString(),point2s);
            }
        }
        ArrayList<PointVO> nodeList = new ArrayList<>();
        relationPoint.forEach((k,v)->{
            String[] split = k.split("\\)");
            for (int i = 0; i < split.length; i++) {
                String lines = split[i].substring(split[i].indexOf("(")+1);
                String[] pos = lines.split(",");
                if (pos.length == 1) {
                } else {
                    nodeList.add(new PointVO(Double.parseDouble(pos[0]), Double.parseDouble(pos[1])));
                }
            }
        });
        Collections.sort(nodeList, new Comparator<PointVO>() {
            @Override
            public int compare(PointVO o1, PointVO o2) {
                double i = o1.getY() - o2.getY();
                if(i==0){
                    double v = o1.getX() - o2.getX();
                    if(v>0){
                        return 1;
                    }else if(v==0) {
                        return 0;
                    }else {
                        return -1;
                    }
                }
                if(i>0){
                    return 1;
                }else {
                    return -1;
                }
            }
        });

        if(nodeList.size()>0){
            PointVO point = nodeList.get(0);
            boundList.add(point);
        }
        PointVO bottomPoint=null;
        if(boundList.size()>0){
            bottomPoint = boundList.get(0);
        }
        Map<String, Double> tempLi = new HashMap<>();
        boundList.forEach(b->{
            List<PointVO> points = relationPoint.get(b.toString());
            points.forEach(p->{
                double v = CommonUtil.calculatePolarAngle(CommonUtil.vector2d(b, p));
                tempLi.put(p.toString(),v);
            });
        });
        Map<String, Double> kvMap = CommonUtil.sortDescMapByValues(tempLi);
        Map.Entry entry = kvMap.entrySet().iterator().next();
        String key = (String)entry.getKey();
        String[] split = key.split("\\)");
        for (int i = 0; i < split.length; i++) {
            String lines = split[i].substring(split[i].indexOf("(")+1);
            String[] pos = lines.split(",");
            if (pos.length == 1) {
            } else {
                boundList.add(new PointVO(Double.parseDouble(pos[0]), Double.parseDouble(pos[1])));
            }
        }
        int num=0;
        while (true){
            num++;
            if(num>100){
                //如果循环了一百次 还无法找到就退出 避免死循环
                break;
            }
            PointVO p1 = boundList.get(boundList.size() - 1);
            PointVO p2 = boundList.get(boundList.size() - 2);
            Vector2VO vector2Vo = CommonUtil.vector2d(p1, p2);
            List<PointVO> points = relationPoint.get(p1.toString());
            Iterator<PointVO> iterator = points.iterator();
            while (iterator.hasNext()){
                PointVO next = iterator.next();
                if(next.toString().equals(p2.toString())){
                    iterator.remove();
                }
            }
            PointVO nextP=null;
            if(points.size()>1){
                PointVO point = points.get(0);
                double v = CommonUtil.calculateVectorAngle(vector2Vo, CommonUtil.vector2d(p1, point));
                for (int i = 1; i < points.size(); i++) {
                    double v1 = CommonUtil.calculateVectorAngle(vector2Vo, CommonUtil.vector2d(p1, points.get(i)));
                    if(v1>v){
                        v=v1;
                        nextP=points.get(i);
                    }
                }
                if(nextP==null){
                    nextP=point;
                }
            }else {
                return new ArrayList<PointVO>();
            }
            if(nextP!=null) {
                boundList.add(nextP);
                if (bottomPoint.equals(nextP)) {
                    break;
                }
            }
        }
        if(num>100){
            return new ArrayList<PointVO>();
        }
        return boundList;
    }


    /**
     * 等高的点生成边
     * @param points 3D坐标
     * @return
     */
    public static List<List<PointVO>> generateEdges(List<PointVO> points, VectorVO vectorPo){
        if(points.size()>0){
            PointVO point = points.get(0);
            PointVO point1 = points.get(points.size() - 1);
            if(point.equals(point1)){
                points.remove(0);
            }
        }
        List<List<PointVO>> edges = new ArrayList<>();
        int num = points.size();
        List<PointVO> upPoints=new ArrayList<>(points);

        List<double[]> nPoints = new ArrayList<>();
        points.forEach(calendarPoint -> {
            double[] li1 = {calendarPoint.getX(), calendarPoint.getY(), calendarPoint.getZ(), 1};
            nPoints.add(li1);
        });
        VectorVO light = new VectorVO();
        light.setA(0d);
        light.setB(0d);
        light.setC(-1d);
        light.setD(0d);
        //构建投影矩阵
        double[][] matArrays = CommonUtil.buildProjectionMat(vectorPo, light);
        //坐标2维数组
        double[][] doubles5 = CommonUtil.changeListToArrays1(nPoints);
        //矩阵倒转
        double[][] doubles6 = CommonUtil.transposeMatrix(doubles5);
        //矩阵乘积
        double[][] dot1 = CommonUtil.dot(matArrays, doubles6);
        double[][] doubles7 = CommonUtil.transposeMatrix(dot1);
        //矩阵缩放
        double[][] suo = CommonUtil.suo(doubles7);
        ArrayList<PointVO> downPoints = new ArrayList<>();
        for (int i = 0; i < suo.length; i++) {
            double[] doubles = suo[i];
            downPoints.add(new PointVO(doubles[0],doubles[1],doubles[2]));
        }
        upPoints.add(upPoints.get(0));
        downPoints.add(downPoints.get(0));
        for (int i = 0; i < num ; i++) {
            ArrayList<PointVO> points1 = new ArrayList<>();
            PointVO point = upPoints.get(i);
            PointVO point1 = upPoints.get(i + 1);
            points1.add(point);
            points1.add(point1);
            ArrayList<PointVO> points2 = new ArrayList<>();
            PointVO point2 = downPoints.get(i);
            PointVO point3 = downPoints.get(i + 1);
            points2.add(point2);
            points2.add(point3);
            ArrayList<PointVO> points3 = new ArrayList<>();
            points3.add(point);
            points3.add(point2);
            edges.add(points1);
            edges.add(points2);
            edges.add(points3);
        }

        return edges;
    }

    /**
     * 冬至日 夏至日时刻
     * @return
     */
    public static List<String> winterOrSumerTimeFor22(double lat){
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        ArrayList<String> allTimes = new ArrayList<>();
        String[] times={" 09:00:00"," 10:00:00"," 11:00:00"," 12:00:00"," 13:00:00"," 14:00:00"," 15:00:00"};
        String[] dates={"12-22"};
        if(lat<0){
            dates = new String[]{"6-22"};
        }
        for (String date:dates
        ) {
            for (String time:times
            ) {
                String all=year+"-"+date+time;
                allTimes.add(all);
            }
        }
        return allTimes;
    }

    /**
     * 重组线段
     */
    public static List<List<PointVO>> reorganizeSegments(List<List<PointVO>> edges){
        HashMap<String, List<PointVO>> edgeForPoints = new LinkedHashMap<>();
        for (List<PointVO> e:edges) {
            String s = e.toString();
            edgeForPoints.put(s,e);
        }
        List<List<PointVO>> edgeBaks=new ArrayList<>();
        for (List<PointVO> e:edges
        ) {
            List<PointVO> edgeBak = new ArrayList<>();
            for (PointVO p:e) {
                edgeBak.add(new PointVO(p.getX(), p.getY()));

            }
            edgeBaks.add(edgeBak);
        }

        edgeBaks.forEach(e1->{
            edgeBaks.forEach(e2->{
                if(!e1.equals(e2)) {
                    //筛选出无公共点的线段组合
                    if (!onePoint(e1, e2)) {
                        LineString lines = PointVO.createLines(e1);
                        LineString lines1 = PointVO.createLines(e2);
                        if (lines.intersects(lines1)) {
                            Geometry intersection = lines1.intersection(lines);
                            String geometryType = intersection.getGeometryType();
                            if (geometryType.equalsIgnoreCase(Geometry.TYPENAME_POINT)) {
                                Coordinate coordinate = intersection.getCoordinate();
                                List<PointVO> points = edgeForPoints.get(e1.toString());
                                if (points != null && points.size() > 0) {
                                    points.add(new PointVO(coordinate.x, coordinate.y));
                                    HashSet<PointVO> points2 = new HashSet<>();
                                    points2.addAll(points);
                                    points.removeAll(points);
                                    points.addAll(points2);
                                }
                                List<PointVO> points1 = edgeForPoints.get(e2.toString());
                                if (points1 != null && points1.size() > 0) {
                                    points1.add(new PointVO(coordinate.x, coordinate.y));
                                    HashSet<PointVO> points2 = new HashSet<>();
                                    points2.addAll(points1);
                                    points1.removeAll(points1);
                                    points1.addAll(points2);
                                }
                            }

                        }
                    }
                }
            });
        });
        List<List<PointVO>> newList = new ArrayList<>();
        edgeForPoints.forEach((k,v)->{
            if(v.size()>2){
                v.sort(Comparator.comparing(PointVO::getX).thenComparing(PointVO::getY));
                for (int i = 0; i < v.size()-1; i++) {
                    ArrayList<PointVO> objects = new ArrayList<>();
                    objects.add(v.get(i));
                    objects.add(v.get(i+1));
                    newList.add(objects);
                }
            }else {
                ArrayList<PointVO> objects = new ArrayList<>();
                String[] split = k.split("\\)");
                for (int i = 0; i < split.length; i++) {
                    String lines = split[i].substring(split[i].indexOf("(")+1);
                    String[] pos = lines.split(",");
                    if (pos.length == 1) {
                    } else {
                        objects.add(new PointVO(Double.parseDouble(pos[0]), Double.parseDouble(pos[1])));
                    }
                }
                newList.add(objects);
            }
        });
        return newList;
    }

    /**
     * 两个线段有公共的点
     * @param p1
     * @param p2
     * @return
     */
    public static boolean onePoint(List<PointVO> p1,List<PointVO> p2){
        HashSet<PointVO> points = new HashSet<>();
        points.addAll(p1);
        points.addAll(p2);
        int sum = p1.size() + p2.size();
        if(points.size()!=sum){
            return true;
        }
        return false;

    }






}
